package com.gitee.wsl.ext.string

import kotlin.math.min


fun String.isAbsolutePath(): Boolean {
    //if (path == null) return false
    val path = this

    if (!path.startsWith("/")) return false

    if (path.indexOf('\\') >= 0 || path.indexOf(':') >= 0) return false

    if (path.contains("/./")) return false
    if (path.contains("/../")) return false
    if (path.endsWith("/.") || path.endsWith("/..")) return false
    return true
}


fun String?.isCanonicalFilePath(): Boolean {
    val name = this
    if (name == null) return false

    if (name.indexOf('\\') >= 0) return false

    if (name.contains("../")) return false

    if (name.endsWith("/..")) return false

    if (name.contains("/./")) return false

    if (name.startsWith("./") || name.endsWith("/.")) return false

    return isValidFilePath(name)
}


private val INVALID_FILE_PATH_CHARS = charArrayOf('\\', ':', '*', '?', '"', '<', '>', '|', 0.toChar())

private val INVALID_FILE_PATH_CHARS_STR = INVALID_FILE_PATH_CHARS.joinToString()

fun isValidFilePath(path: String): Boolean {
    if (path.isEmpty()) return false
    if (_containsControlAscii(path)) return false
    return !containsAnyChar(path, INVALID_FILE_PATH_CHARS_STR) && !path.contains("//")
}


/**
 * Normalize the path by suppressing sequences like "path/.." and inner simple dots.
 *
 *
 * The result is convenient for path comparison. For other uses, notice that Windows separators ("\") are replaced
 * by simple slashes.
 *
 * @param path the original path
 * @return the normalized path
 */
fun String.normalizePath(): String {
    val path = this
    var pathToUse = path.replace('\\', '/')

    // Strip prefix from path to analyze, to not treat it as part of the
    // first path element. This is necessary to correctly of paths like
    // "file:core/../core/io/Resource.class", where the ".." should just
    // strip the first "core" directory while keeping the "file:" prefix.
    val prefixIndex = pathToUse.indexOf(":")
    var prefix = ""
    if (prefixIndex != -1) {
        prefix = pathToUse.take(prefixIndex + 1)
        if (prefix.contains("/")) {
            prefix = ""
        } else {
            pathToUse = pathToUse.substring(prefixIndex + 1)
        }
    }
    if (pathToUse.isEmpty()) return prefix

    // 不包含/./或者/../, 就没有必要再做后续处理
    if (pathToUse.indexOf("/.") < 0) {
        return if (prefix.length <= 0) pathToUse else prefix + pathToUse
    }

    if (pathToUse.get(0) == '/') {
        prefix = "$prefix/"
        pathToUse = pathToUse.substring(1)
    }

    val pathArray = pathToUse.split( '/')
    val pathElements = ArrayDeque<String>(pathArray.size)
    var tops = 0

    for (i in pathArray.size - 1 downTo 0) {
        val element = pathArray[i]

        if ("." == element) { //NOPMD - suppressed EmptyControlStatement
            // Points to current directory - drop it.
        } else if (".." == element) {
            // Registering top path found.
            tops++
        } else {
            if (tops > 0) {
                // Merging path element with element corresponding to top
                // path.
                tops--
            } else {
                // Normal path element found.
                // element = replace(element, "..", "__");
                pathElements.addFirst(element)
            }
        }
    }

    // ignore all top paths need to be retained.
    return prefix + pathElements.joinToString( "/")
}

fun String?.appendPath(relativePath: String?): String? {
    val path = this
    if (relativePath == null || relativePath.length <= 0) return path

    if (path == null || path.isEmpty()) return relativePath

    if (!path.endsWith("/")) {
        if (!relativePath.startsWith("/")) {
            return "$path/$relativePath"
        } else {
            return path + relativePath
        }
    } else {
        if (!relativePath.startsWith("/")) {
            return path + relativePath
        } else {
            return path + relativePath.substring(1)
        }
    }
}

fun relativizePath(base: String?, path: String?): String? {
    var base = base
    var path = path
    if (path == null) return null
    if (base == null) return path

    if (!path.startsWith("/") || !base.startsWith("/")) return path
    base = base.substring(1)
    path = path.substring(1)

    val baseIsDir = base.endsWith("/")
    val relativeIsDir = path.endsWith("/")
    if (baseIsDir) base = base.dropLast(1)
    if (base.length <= 0) return path

    if (relativeIsDir) path = path.dropLast(1)
    val baseParts = base.split( '/')
    val relativeParts = path.split( '/')
    val n: Int = min(baseParts.size, relativeParts.size)
    var i: Int = 0
    while (i < n) {
        if (!baseParts.get(i).equals(relativeParts.get(i))) break
        i++
    }
    val sb = StringBuilder()
    run {
        var j = if (baseIsDir) i else i + 1
        val K: Int = baseParts.size
        while (j < K) {
            sb.append("..")
            if (j != K - 1) sb.append('/')
            j++
        }
    }
    var j = i
    val K: Int = relativeParts.size
    while (j < K) {
        if (sb.isNotEmpty()) sb.append('/')
        sb.append(relativeParts.get(j))
        j++
    }
    if (relativeIsDir) sb.append('/')
    return sb.toString()
}

fun toAbsolutePath(path: String?, currentPath: String?): String? {
    return absolutePath(currentPath, path)
}


fun absolutePath(currentPath: String?, path: String?): String? {
    if (currentPath == null) return path?.normalizePath()
    if (path == null) return null

    val pos0 = path.indexOf(':')
    // 带协议
    if (pos0 >= 0) {
        // 协议相同
        /*
             * int pos1 = currentPath.indexOf(':'); if(pos1 == pos0 && path.regionMatches(0, currentPath, 0, pos0)){
             * if(pos0 == path.length() - 1){ return path + "/"; }else if(path.charAt(pos0+1) == '/'){ // 本身就是绝对路径
             * return path; }else{ return _appendPath(currentPath, path.substring(pos0+1)); } }else{ // 协议不相同，则不合并路径
             * return normalizePath(path); }
             */
        // 带协议就不进行路径合并
        return path.normalizePath()
    }

    // 如果是绝对路径，则直接返回
    if (path.startsWith("/")) return path.normalizePath()

    return _appendPath(currentPath, path)
}

private fun _appendPath(currentPath: String, path: String?): String {
    if (currentPath.endsWith("/")) return (currentPath + path).normalizePath()

    var pos = currentPath.lastIndexOf('/')
    if (pos < 0) {
        pos = currentPath.indexOf(':')
        if (pos > 0) return (currentPath.take(pos + 1) + path).normalizePath()
        return ("/$path").normalizePath()
    } else {
        return (currentPath.take(pos + 1) + path).normalizePath()
    }
}

fun pathStartsWith(p1: String, p2: String?): Boolean {
    if (p1.isEmpty()) return false
    if (p2 == null) return false

    if (!p1.startsWith(p2)) return false
    if (p1.length == p2.length) return true
    if (p2.endsWith("/")) return true
    val c = p1[p2.length].code
    return c == '/'.code
}


fun pathEndsWith(p1: String, p2: String?): Boolean {
    if (p1.isEmpty()) return false
    if (p2 == null) return false

    if (!p1.endsWith(p2)) return false

    if (p1.length == p2.length) return true
    if (p2.startsWith("/")) return true
    val c = p1[p1.length - p2.length].code
    return c == '/'.code
}